﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace VsTortoiseSVN.Dialogs
{
    public partial class OpenModifiedDocumentDialog : Form, IMessageFilter
    {
        private DTE2 _dte { get; set; }
        private SvnVsProjectStatus _vsStatusProject { get; set; }
        private SvnHelperStatusItem[] _statusItems { get; set; }
        private Window _lastOpenedWindow { get; set; }
        private ImageList _smallFileIcons { get; set; }

        private SvnHelperStatusItem[] _selection
        {
            get
            {
                SvnHelperStatusItem[] items = new SvnHelperStatusItem[listViewDocs.SelectedItems.Count];

                for (int n = 0; n < listViewDocs.SelectedItems.Count; ++n)
                {
                    items[n] = listViewDocs.SelectedItems[n].Tag as SvnHelperStatusItem;
                }

                return items;
            }
        }

        private string[] _selectedFilenames
        {
            get
            {
                SvnHelperStatusItem[] selection = this._selection;
                string[] filenames = new string[selection.Length];

                for (int n = 0; n < selection.Length; ++n)
                {
                    filenames[n] = selection[n].Status.FullPath;
                }

                return filenames;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public OpenModifiedDocumentDialog(DTE2 dte)
            : base()
        {
            InitializeComponent();           

            _dte = dte;

            _smallFileIcons = new ImageList();
            _smallFileIcons.ColorDepth = ColorDepth.Depth32Bit;
            _smallFileIcons.ImageSize = new Size(16, 16);
            listViewDocs.SmallImageList = _smallFileIcons;
        }

        private void WorkCompletedMethod(object sender, RunWorkerCompletedEventArgs e)
        {
            UpdateListView();
        }
        
        private void ProgressChangedMethod(object sender, ProgressChangedEventArgs e)
        {
            Text = string.Format("Retrieving file status information ... {0}%", e.ProgressPercentage);
        }


        /// <summary>
        /// Used to forward KeyDown / KeyUp Messages send to
        /// the filter TextBox Control to the ListView as well. This
        /// allows for example to navigate in the ListView with the Cursor keys
        /// while being actually in the TextBox.
        /// </summary>
        public bool PreFilterMessage(ref Message msg)
        {
            if (ActiveControl == textBoxFilter)
            {
                switch(msg.Msg)
                {
                    case Win32.WM_KEYUP:
                    case Win32.WM_KEYDOWN:
                        if ((Keys)msg.WParam == Keys.Up || (Keys)msg.WParam == Keys.Down)
                        {
                            // Only catch cursor up/down keys
                            Win32.SendMessage(listViewDocs.Handle, msg.Msg, msg.WParam, msg.LParam);
                            return true;
                        }
                        break;

                    default:
                        break;
                }
            }

            switch (msg.Msg)
            {
                case Win32.WM_KEYDOWN:
                    if ((Keys)msg.WParam == Keys.F5 && !_vsStatusProject.IsBusy)
                    {
                        Scan();
                        return true;
                    }
                    break;
            }

            return false;
        }

        private void OpenModifiedDocumentDialog_Shown(object sender, EventArgs e)
        {
            Application.AddMessageFilter(this);
            ActiveControl = textBoxFilter;
            textBoxFilter.SelectAll();
        }

        private void OpenModifiedDocumentDialog_FormClosed(object sender, FormClosedEventArgs e)
        {
            _vsStatusProject.Abort();

            Application.RemoveMessageFilter(this);
        }

        private void textBoxFilter_TextUpdate(object sender, EventArgs e)
        {
            if (_vsStatusProject.IsBusy)
                return;

            UpdateListView();
        }

        public void Scan()
        {
            Text = "Retrieving file status information, please wait ...";
            buttonOk.Enabled = false;
            listViewDocs.Items.Clear();

            // Collect references to each project in the Solution
            List<Project> projects = new List<Project>();
            foreach (Project project in _dte.Solution.Projects)
                projects.Add(project);

            // We are interested in svn-modified files only
            SvnHelperStatusBools statusBools = SvnHelperStatusBools.CreateEmpty();
            statusBools.Modified = true;
            statusBools.Added = true;

            // Create and start the new project status thread helper
            _vsStatusProject = new SvnVsProjectStatus(projects, statusBools);
            _vsStatusProject.ProgressChanged += ProgressChangedMethod;
            _vsStatusProject.RunWorkerCompleted += WorkCompletedMethod;
            _vsStatusProject.Start();
        }

        private void UpdateListView()
        {
            if (listViewDocs.InvokeRequired)
            {
                listViewDocs.Invoke(new MethodInvoker(UpdateListView));
                return;
            }

            bool error;
            _statusItems = _vsStatusProject.Filter(textBoxFilter.Text, out error);
            if (_statusItems == null)
            {
                buttonOk.Enabled = false;
                return;
            }

            listViewDocs.BeginUpdate();
            try
            {
                listViewDocs.Items.Clear();

                for (int n = 0; n < _statusItems.Length; ++n)
                {
                    SvnHelperStatusItem statusItem = _statusItems[n];

                    // Only add files to the list. Without this check the list
                    // would display folders as well, which is confusing because
                    // those cannot be opened in Visual Studio.
                    if(statusItem.Status.NodeKind == SharpSvn.SvnNodeKind.File)
                    {
                        if (!_smallFileIcons.Images.ContainsKey(statusItem.Info.Extension))
                        {
                            Icon smallIcon = Win32.GetSmallIcon(statusItem.Info.FullName);
                            _smallFileIcons.Images.Add(statusItem.Info.Extension, smallIcon);
                        }

                        ListViewItem item = new ListViewItem(statusItem.Info.Name);
                        item.ImageKey = statusItem.Info.Extension;
                        item.Tag = statusItem;
                        item.SubItems.Add(statusItem.Info.FullName);
                        item.SubItems.Add(statusItem.Status.LocalContentStatus.ToString());
                        item.SubItems.Add(statusItem.Info.LastWriteTime.ToShortDateString() + " " + statusItem.Info.LastWriteTime.ToShortTimeString());

                        listViewDocs.Items.Add(item);
                    }
                }
            }
            finally
            {
                listViewDocs.EndUpdate();
            }

            textBoxFilter.BackColor = error ? Color.IndianRed : SystemColors.Window;
            textBoxFilter.ForeColor = error ? Color.White : SystemColors.WindowText;

            buttonOk.Enabled = false;
            Text = string.Format("Open modified File [{0} of {1}]", _statusItems.Length, _vsStatusProject.Count);
        }

        private void buttonOk_Click(object sender, EventArgs e)
        {
            _lastOpenedWindow = null;

            SvnHelperStatusItem[] selection = _selection;
            foreach (var statusItem in selection)
            {
                try
                {
                    _lastOpenedWindow = _dte.ItemOperations.OpenFile(statusItem.Info.FullName, Constants.vsViewKindPrimary);
                }
                catch //(System.Exception ex)
                { }
            }

            DialogResult = DialogResult.OK;
            Close();

            closeTimer.Enabled = true;
        }

        private void listViewDocs_SelectedIndexChanged(object sender, EventArgs e)
        {
            buttonOk.Enabled = listViewDocs.SelectedItems.Count > 0;
        }

        private void closeTimer_Tick(object sender, EventArgs e)
        {
            Timer timer = sender as Timer;
            timer.Enabled = false;

            if (_lastOpenedWindow != null)
                _lastOpenedWindow.Activate();
        }

        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {
            string[] selection = _selectedFilenames;

            updateToolStripMenuItem.Enabled = selection.Length > 0;
            commitToolStripMenuItem.Enabled = selection.Length > 0;

            diffToolStripSeparator.Visible = selection.Length == 1;
            diffToolStripMenuItem.Visible = selection.Length == 1;
            diffWithPreviousVersionToolStripMenuItem.Visible = selection.Length == 1;

            showLogToolStripMenuItem.Visible = selection.Length == 1;
            exploreToolStripMenuItem.Visible = selection.Length == 1;

            revertToolStripSeparator.Visible = selection.Length > 0;
            revertToolStripMenuItem.Visible = selection.Length > 0;

            blameToolStripSeparator.Visible = selection.Length == 1;
            blameToolStripMenuItem.Visible = selection.Length == 1;
        }

        private void diffToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = this._selectedFilenames;
            TortoiseProc.Diff(selection[0]);
        }

        private void diffWithPreviousVersionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.DiffWithPreviousVersion(selection[0]);
        }

        private void showLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.ShowLog(selection[0]);
        }

        private void blameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Blame(selection[0]);
        }

        private void revertToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = this._selectedFilenames;
            TortoiseProc.Revert(selection);
        }

        private void updateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = this._selectedFilenames;
            TortoiseProc.Update(selection);
        }

        private void commitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = this._selectedFilenames;
            TortoiseProc.Commit(selection);
        }

        private void exploreToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string args = "/select," + _selection[0].Status.FullPath;
            Win32.ShellExecute("explorer.exe", args);
        }

        private void SelectAll()
        {
            ListView.ListViewItemCollection items = listViewDocs.Items;
            foreach (ListViewItem item in items)
            {
                item.Selected = true;
            }
        }

        private void listViewDocs_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == (Keys.Control | Keys.A))
            {
                SelectAll();
                e.Handled = true;
            }
        }

//         private void listViewDocs_ItemMouseHover(object sender, ListViewItemMouseHoverEventArgs e)
//         {
//              string unifiedDiff;
//              SvnHelperStatusItem status = e.Item.Tag as SvnHelperStatusItem;
//              if (SvnHelper.UnifiedLocalDiff(status.Status.FullPath, out unifiedDiff))
//              {
//                  if (!string.IsNullOrEmpty(unifiedDiff))
//                  {
//                      int MAX_LENGTH = 1024;
//                      if (unifiedDiff.Length > MAX_LENGTH)
//                      {
//                          unifiedDiff = unifiedDiff.Substring(0, MAX_LENGTH);
//                          unifiedDiff += "\n...";
//                      }
//                      toolTip1.SetToolTip(listViewDocs, unifiedDiff);
//                  }
//                  else
//                      toolTip1.SetToolTip(listViewDocs, string.Empty);
//              }
//         }
    }

    
    public class SvnVsProjectStatus
    {
        private List<SvnHelperStatusItem> Items { get; set; }
        private List<Project> Projects { get; set; }
        private SvnHelperStatusBools StatusBools { get; set; }
        private BackgroundWorker Worker = new BackgroundWorker();

        public event ProgressChangedEventHandler ProgressChanged
        {
            add { Worker.ProgressChanged += value; }
            remove { Worker.ProgressChanged -= value; }
        }

        public event RunWorkerCompletedEventHandler RunWorkerCompleted
        {
            add { Worker.RunWorkerCompleted += value; }
            remove { Worker.RunWorkerCompleted -= value; }
        }

        public SvnVsProjectStatus(ICollection<Project> projects, SvnHelperStatusBools statusBools)
        {
            Projects = new List<Project>();
            foreach (Project project in projects)
            {
                try
                {
                    if (!string.IsNullOrEmpty(project.FullName))
                        Projects.Add(project);
                }
                catch //(System.Exception ex)
                {}
            }

            StatusBools = statusBools;
            
            Items = new List<SvnHelperStatusItem>(512);

            Worker.WorkerReportsProgress = true;
            Worker.WorkerSupportsCancellation = true;
            Worker.DoWork += new DoWorkEventHandler(WorkMethod);
        }

        public bool IsBusy
        {
            get
            {
                return Worker.IsBusy;
            }
        }

        public int Count
        {
            get
            {
                return Items.Count;
            }
        }

        public void Start()
        {
            if (IsBusy)
                Worker.CancelAsync();

            Worker.RunWorkerAsync();
        }

        public void Abort()
        {
            if (Worker.IsBusy)
                Worker.CancelAsync();
        }

        public SvnHelperStatusItem[] Filter(string pattern, RegexOptions options, out bool error)
        {
            error = false;

            lock (Items)
            {
                // When no filter pattern has been specified
                // then display the entire list.
                if (string.IsNullOrEmpty(pattern))
                {
                    return Items.ToArray();
                }

                // Filter pattern has been specified.
                // Now check for any matches and only add items on a match.
                List<SvnHelperStatusItem> filtered = new List<SvnHelperStatusItem>(Items.Count);

                try
                {
                    Regex regex = new Regex(pattern, options);
                    foreach (SvnHelperStatusItem item in Items)
                    {
                        if (regex.IsMatch(item.Info.Name))
                            filtered.Add(item);
                    }
                }
                catch
                {
                    // If an exception occurs, return at least
                    // the items found till then.
                    error = true;
                    return filtered.ToArray();
                }

                return filtered.ToArray();
            }
        }

        public SvnHelperStatusItem[] Filter(string pattern, out bool error)
        {
            return Filter(pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.IgnoreCase, out error);
        }

        private void WorkMethod(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker myBw = sender as BackgroundWorker;
            Items.Clear();

            // Get a list of all folders in the project
            List<string> folders = new List<string>(128);
            foreach (Project project in Projects)
            {
                project.GetDirectoryNames(folders);
            }

            // Collapse folder list, this means sub directories
            // are being removed.
            List<string> collapsedFolders = new List<string>();
            PathHelper.RemoveSubDirectories(collapsedFolders, folders);

            // Get the SVN status for each folder
            foreach (string folder in collapsedFolders)
            {
                if (myBw.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }

                SvnHelper.Status(Items, folder, StatusBools);
            }

            e.Result = true;
        }
    }
}
